from __future__ import annotations
from ctypes import Union
import enum
import json
import os
import pathlib
import time
import os
import aiofiles
from .chunk import ChunkBase


class FileState(enum.Enum):
    Completed=0
    Broken=1
    New=2

upload_dir = pathlib.Path(os.environ.get('upload_dir', None))
if not upload_dir:
    raise "Upload directory not set!"

if not os.path.exists(upload_dir):
    os.makedirs(upload_dir)

class UploadRecord:
    _cache: dict[int, UploadRecord] = {}

    def __init__(self, file_name:str,fid: int, offset: int,file_state:FileState=FileState.New) -> None:
        self.file_name=file_name
        self.fid = fid
        self.offset = offset
        self.state=file_state

    def json(self):
        return json.dumps({
            "fid":self.fid,
            "offset":self.offset,
            "state":self.state.value,
        })

    @staticmethod
    def make_fid(name: str, size: int):
        res = size
        buffer = name.encode('utf8')
        for b in buffer:
            res *= b
        return res % (2**32-1)

    @staticmethod
    def make_file_path(file_name: str):
        file_path = upload_dir/file_name
        file_path = str(file_path)
        if os.path.exists(file_path) and os.path.isfile(file_path):
            dot_pos = file_path.rfind('.')
            timestamp = f'[{time.time():.0f}]'
            if dot_pos < 0:
                file_path = file_path+timestamp
            else:
                file_path = file_path[:dot_pos]+timestamp+file_path[dot_pos:]
        return file_path

    @staticmethod
    def make_record(file_name: str, file_size: int,overwrite=False) -> UploadRecord:
        file_name = file_name.strip()
        dest_path=upload_dir/file_name
        fid = UploadRecord.make_fid(file_name, file_size)
        if os.path.exists(dest_path):
            if overwrite:
                os.remove(dest_path)
                temp_path=upload_dir/(str(fid)+'.temp')
                if os.path.exists(temp_path):
                    os.remove(temp_path)
            else:
                return UploadRecord(file_name,-1,-1,FileState.Completed)
        if fid in UploadRecord._cache:
            return UploadRecord._cache[fid]
        temp_path = upload_dir/(str(fid)+'.temp')
        if not temp_path.is_file():
            offset = 0
            state=FileState.New
        else:
            offset = temp_path.stat().st_size
            state=FileState.Broken

        record = UploadRecord(file_name,fid, offset,state)
        UploadRecord._cache[fid]=record
        return record

    @staticmethod
    def load(fid:int) -> UploadRecord:
        return UploadRecord._cache.get(fid,None)

    @property
    def is_complete(self):
        return self.state==FileState.Completed    
    
    @property
    def temp_path(self):
        return upload_dir/(str(self.fid)+'.temp')

    async def save_chunk(self, chunk: ChunkBase):
        if self.offset != chunk.offset:
            raise f'Wrong chunk offset[{chunk.offset}], expect [{self.offset}]'
        async with aiofiles.open(self.temp_path, 'ab') as f:
            await f.seek(chunk.offset)
            await f.write(chunk.get_bytes())
        self.offset+=chunk.length
        print('Save chunk:', self.fid, chunk.offset, chunk.length)
    
    def complete(self):
        dest_path=upload_dir/self.file_name
        os.rename(self.temp_path,dest_path)
        del UploadRecord._cache[self.fid]
        print('Complete Uploading:',self.fid,self.file_name)
